
import {
    exists,
    getDirnameOf,
    joinPath,
    mkdir,
    readFile,
    toPosix,
    writeFile,
    writeImgByBase64
} from "../../function/fileFunc";
import path from "path";
import {getAppObj} from "../../function/appFunc";
import { requestText } from "../../function/httpFunc";
import {message} from "antd";

export namespace MangaSite {
    export type TSiteId = "" | "tonariYJ" | "GanGanOnline" | "local" | "ManHuaDui" | "DMZJ";
    export interface IArticleBaseInfo {
        id: number;
        article_index: number;
        book_id: number;
        origin_url: string;
        download_method: TSiteId;
    }

    export interface ImgItemInfo {
        imgIndex: number;
        type: TSiteId;
        src: string;
    }

    export interface ImgCacheMap {
        [url: string]: HTMLImageElement;
    }

    export type TDrawPosition = "left" | "right";
    export type SaveAsType = "jpg" | "jpeg" | "png";
}

export abstract class MangaSite {
    public readonly canvas: HTMLCanvasElement;
    public readonly drawer: CanvasRenderingContext2D;
    public readonly articleInfo: MangaSite.IArticleBaseInfo;
    protected imgCacheMap: MangaSite.ImgCacheMap = {};
    protected scale: number = 1;
    protected imgShowSize = {
        width: 0,
        height: 0,
    };
    protected copyCanvas: HTMLCanvasElement;
    protected copyDrawer: CanvasRenderingContext2D;
    constructor(canvas: HTMLCanvasElement, articleInfo: MangaSite.IArticleBaseInfo) {
        this.canvas = canvas;
        this.drawer = canvas.getContext("2d");
        this.articleInfo = {
            id: articleInfo.id,
            article_index: articleInfo.article_index,
            book_id: articleInfo.book_id,
            origin_url: articleInfo.origin_url,
            download_method: articleInfo.download_method,
        };
    }
    public getImgCacheMap(): MangaSite.ImgCacheMap {
        return {
            ...this.imgCacheMap,
        };
    }
    public getImgElmFromCache(srcKey: string): HTMLImageElement {
        return this.imgCacheMap[srcKey];
    }
    public setImgElmCacheItem(srcKey: string, img: HTMLImageElement): this {
        this.imgCacheMap[srcKey] = img;
        return this;
    }
    public abstract drawPage(
        img: HTMLImageElement,
        position?: MangaSite.TDrawPosition,
        showPageIndexLen?: number
    ): this;
    public abstract getImgList(): Promise<MangaSite.ImgItemInfo[]>;
    public getArticleInfoJSONFile(): string {
        const info = this.articleInfo;
        return toPosix(
            path.resolve(
                path.dirname(getAppObj().getAppPath()),
                "book_json/" + "id_" + info.book_id + "/article_" + info.article_index + ".json"
            )
        );
    }
    public async getJSON(): Promise<string> {
        const jsonFileName = this.getArticleInfoJSONFile();
        const existFile = await exists(jsonFileName);
        if (existFile) {
            return readFile(jsonFileName);
        }
        const dirname = path.dirname(jsonFileName);
        const existDir = await exists(dirname);
        if (!existDir) {
            await mkdir(dirname);
        }
        return this.cacheArticleInfoJSONFile(jsonFileName);
    }

    public async loadImg(
        imgInfo: MangaSite.ImgItemInfo,
        img?: HTMLImageElement
    ): Promise<HTMLImageElement> {
        const originSrc = imgInfo.src;
        if (Object.prototype.hasOwnProperty.call(this.imgCacheMap, originSrc)) {
            return this.loadImgFromCache(imgInfo);
        }
        const picRelName = this.getPicRelName(imgInfo.imgIndex);
        const absName = toPosix(joinPath(getDirnameOf(getAppObj().getAppPath()), picRelName));
        const absImgExist = await exists(absName);
        if (absImgExist) {
            return this.loadImgFromSave(imgInfo, picRelName, img);
        }
        return this.loadImgFromTmp(imgInfo, img);
    }
    public buildImgFrom(
        imgSrc: string,
        siteId: MangaSite.TSiteId,
        img: HTMLImageElement = new Image()
    ): Promise<HTMLImageElement> {
        return new Promise<HTMLImageElement>((resolve, reject) => {
            img.addEventListener("load", () => {
                this.imgCacheMap[imgSrc] = img;
                resolve(img);
            });
            img.addEventListener("error", (err) => {
                reject(err);
            });
            img.setAttribute("site-id", siteId);
            img.crossOrigin = "Anonymous";
            img.src = imgSrc;
        });
    }
    public getPicRelName(pageIndex: string | number) {
        const articleInfo = this.articleInfo;
        return "pics/id_" + articleInfo.book_id +
            "/article_" + articleInfo.article_index +
            "/page_" + pageIndex + ".jpg";
    }
    public getPicAbsName(pageIndex: string | number) {
        const relName = this.getPicRelName(pageIndex);
        return toPosix(joinPath(getDirnameOf(getAppObj().getAppPath()), relName));
    }
    public writeImgInBase64AutoFillName(nameIndex: string | number, base64Data: string): Promise<void> {
        const name = this.getPicAbsName(nameIndex);
        return this.writeImgInBase64(name, base64Data);
    }
    public writeImgInBase64(name: string, base64Data: string): Promise<void> {
        return writeImgByBase64(name, base64Data);
    }
    public savePart(part: MangaSite.TDrawPosition = "right", saveAs: MangaSite.SaveAsType = "jpg"): string {
        const drawer = this.getCopyDrawer();
        this.setCopyCanvasSize();
        let startX = this.canvas.width / 2 - this.imgShowSize.width;
        if (part === "right") {
            startX = this.canvas.width / 2;
        }
        this.clearCopyCanvas();
        drawer.drawImage(
            this.canvas,
            startX,
            0,
            this.imgShowSize.width,
            this.imgShowSize.height,
            0,
            0,
            this.copyCanvas.width,
            this.copyCanvas.height
        );
        return this.copyCanvas.toDataURL(saveAs);
    }
    public clearCanvas(): this {
        this.drawer.clearRect(0, 0, this.canvas.width, this.canvas.height);
        return this;
    }
    protected async cacheArticleInfoJSONFile(filename: string): Promise<string> {
        const info = this.articleInfo;
        let reqUrl = info.origin_url;
        if (info.download_method === "tonariYJ") {
            reqUrl += ".json";
        }
        const data = await requestText(reqUrl);
        await writeFile(filename, data);
        return Promise.resolve(data);
    }
    protected getCopyCanvas(): HTMLCanvasElement {
        if (this.copyCanvas) {
            return this.copyCanvas;
        }
        const canvas = document.createElement("canvas");
        this.copyCanvas = canvas;
        return this.copyCanvas;
    }
    protected getCopyDrawer() {
        if (!this.copyDrawer) {
            if (!this.copyCanvas) {
                this.getCopyCanvas();
            }
            this.copyDrawer = this.copyCanvas.getContext("2d");
        }
        return this.copyDrawer;
    }
    protected setCopyCanvasSize(): this {
        const { width, height } = this.imgShowSize;
        this.copyCanvas.width = width;
        this.copyCanvas.height = height;
        return this;
    }
    protected clearCopyCanvas(): this {
        this.copyDrawer.clearRect(0, 0, this.copyCanvas.width, this.copyCanvas.height);
        return this;
    }
    protected directDrawPage(
        img: HTMLImageElement,
        position: MangaSite.TDrawPosition = "right",
        showPageIndexLen: number = 2
    ) {
        const canvas = this.canvas;
        const cWidth = canvas.width;
        const cHeight = canvas.height;
        const imgWidth = img.width;
        const imgHeight = img.height;
        const scale = Math.min(cWidth / 2 / imgWidth, cHeight / imgHeight);
        this.scale = scale;
        this.imgShowSize.width = imgWidth * scale;
        this.imgShowSize.height = imgHeight * scale;
        let leftStart = cWidth / 2 - this.imgShowSize.width;
        if (position === "right") {
            leftStart = cWidth / 2;
        }
        this.drawer.drawImage(
            img,
            0,
            0,
            imgWidth,
            imgHeight,
            leftStart,
            0,
            this.imgShowSize.width,
            this.imgShowSize.height
        );
        return this;
    }
    protected buildImgReqURL(imgInfo: MangaSite.ImgItemInfo) {
        return "akaba:///tmp/" + this.getPicRelName(imgInfo.imgIndex) +
            "?origin=" + encodeURIComponent(imgInfo.src) + "&site-id=" + this.articleInfo.download_method;
    }
    protected loadImgFromTmp(imgInfo: MangaSite.ImgItemInfo, img?: HTMLImageElement): Promise<HTMLImageElement> {
        const akabaSrc = this.buildImgReqURL(imgInfo);
        return this.buildImgFrom(akabaSrc, imgInfo.type, img)
            .then((imgElm) => {
                this.imgCacheMap[imgInfo.src] = imgElm;
                return imgElm;
            });
    }
    protected loadImgFromCache(imgInfo: MangaSite.ImgItemInfo): Promise<HTMLImageElement> {
        return Promise.resolve(this.imgCacheMap[imgInfo.src]);
    }
    protected loadImgFromSave(
        imgInfo: MangaSite.ImgItemInfo,
        picRelName: string,
        img?: HTMLImageElement
    ): Promise<HTMLImageElement> {
        return this.buildImgFrom("akaba:///" + picRelName, "", img)
            .then((imgElm) => {
                this.imgCacheMap[imgInfo.src] = imgElm;
                return imgElm;
            });
    }
}
